package com.joinval.common.utils;

import java.io.*;
import java.net.Socket;
import java.util.Arrays;

/**
 * Ymodem 文件传输
 * 陈家大院
 */
public class YmodemUploader {

    private static final int PACKET_SIZE = 1024;
    private static final byte SOH = 0x01; //128字节数据包
    private static final byte STX = 0x02; //1024字节数据包
    private static final byte EOT = 0x04; //结束传输
    private static final byte ACK = 0x06; //回应
    private static final byte NAK = 0x15; //不回应
    private static final byte CAN = 0x18; //传输终止
    private static final byte C = 0x43; //请求数据包-传输的发送C请求起始帧数据，然后再发送C请求文件数据帧，最后有发送一次C请求结束帧！

    public static void main(String[] args) {


        String path = "/Users/chenyong/Downloads/rtthread.rbl";

        String filePath = path; // 替换为要传输的文件路径
        String host = "localhost"; // 替换为目标主机的IP地址
        int port = 7777; // 替换为目标主机的端口号

        try (Socket socket = new Socket(host, port);
             BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());
             BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream())) {
            File file = new File(filePath);
            if (!file.exists()) {
                System.out.println("文件不存在");
                return;
            }
            sendFile(file, outputStream, inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件传输
     * @param file
     * @param outputStream
     * @param inputStream
     * @throws IOException
     */
    public static void  sendFile(File file, BufferedOutputStream outputStream, BufferedInputStream inputStream) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(file);
        try {
            byte[] buffer = new byte[PACKET_SIZE];
            int packetNumber = 0;
            int bytesRead;

            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                if (bytesRead < PACKET_SIZE) {
                    Arrays.fill(buffer, bytesRead, PACKET_SIZE, (byte) 0x1A); // 填充剩余部分为终止字符
                }

                sendPacket(packetNumber, buffer, outputStream, inputStream);

                packetNumber++;
            }

            sendEOT(outputStream, inputStream);
        } finally {
            fileInputStream.close();
        }
    }

    /**
     *
     * @param packetNumber
     * @param data
     * @param outputStream
     * @param inputStream
     * @throws IOException
     */
    public static void sendPacket(int packetNumber, byte[] data, BufferedOutputStream outputStream, BufferedInputStream inputStream) throws IOException {
        byte[] packet = new byte[PACKET_SIZE + 4];
        packet[0] = SOH;
        packet[1] = (byte) packetNumber;
        packet[2] = (byte) (~packetNumber & 0xFF);

        System.arraycopy(data, 0, packet, 3, PACKET_SIZE);

        int crc = calculateCRC(packet, PACKET_SIZE + 3);
        packet[PACKET_SIZE + 3] = (byte) ((crc >> 8) & 0xFF);
        packet[PACKET_SIZE + 4] = (byte) (crc & 0xFF);

        sendPacketAndWaitForACK(packet, outputStream, inputStream);
    }

    public static void sendEOT(BufferedOutputStream outputStream, BufferedInputStream inputStream) throws IOException {
        outputStream.write(EOT);
        outputStream.flush();

        receiveACK(inputStream);
    }

    public static void sendPacketAndWaitForACK(byte[] packet, BufferedOutputStream outputStream, BufferedInputStream inputStream) throws IOException {
        boolean ackReceived = false;
        int retryCount = 0;

        while (!ackReceived && retryCount < 10) {
            outputStream.write(packet);
            outputStream.flush();

            ackReceived = receiveACK(inputStream);

            retryCount++;
        }

        if (!ackReceived) {
            throw new IOException("未收到ACK确认");
        }
    }

    public static boolean receiveACK(BufferedInputStream inputStream) throws IOException {
        int ack = inputStream.read();

        return ack == ACK;
    }

    public static int calculateCRC(byte[] data, int length) {
        int crc = 0;
        int polynomial = 0x1021;

        for (int i = 0; i < length; i++) {
            crc ^= (data[i] << 8);

            for (int j = 0; j < 8; j++) {
                if ((crc & 0x8000) != 0) {
                    crc = (crc << 1) ^ polynomial;
                } else {
                    crc <<= 1;
                }
            }
        }

        return crc & 0xFFFF;
    }
}
